//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.client.creation;

import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import tv.dyndns.kishibe.client.PlusOne;
import tv.dyndns.kishibe.client.Service;
import tv.dyndns.kishibe.client.UserData;
import tv.dyndns.kishibe.client.Utility;
import tv.dyndns.kishibe.client.game.ProblemGenre;
import tv.dyndns.kishibe.client.game.ProblemType;
import tv.dyndns.kishibe.client.game.RandomFlag;
import tv.dyndns.kishibe.client.packet.PacketBbsResponse;
import tv.dyndns.kishibe.client.packet.PacketBbsThread;
import tv.dyndns.kishibe.client.packet.PacketProblem;
import tv.dyndns.kishibe.client.util.StringUtils;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class WidgetProblemForm extends VerticalPanel implements ClickHandler, ChangeHandler {
	private static final Logger logger = Logger.getLogger(WidgetProblemForm.class.getName());
	private static final int MAX_NUMBER_OF_ANSWERS = 8;
	private static final int MAX_NUMBER_OF_CHOICES = 8;
	private static final int MAX_PROBLEM_NOTE_LENGTH = 1024;
	private final Label labelProblemNumber = new Label("新規問題を作成中");
	private final ListBox listBoxGenre = new ListBox();
	@VisibleForTesting
	final ListBox listBoxType = new ListBox();
	private final ListBox listBoxRandomFlag = new ListBox();
	private final TextArea textAreaSentence = new TextArea();
	@VisibleForTesting
	final TextBox[] textBoxAnswer = new TextBox[MAX_NUMBER_OF_ANSWERS];
	@VisibleForTesting
	final TextBox[] textBoxChoice = new TextBox[MAX_NUMBER_OF_CHOICES];
	private final RadioButton radioButtonNone = new RadioButton("external", "使用しない");
	private final RadioButton radioButtonImage = new RadioButton("external", "画像");
	private final RadioButton radioButtonYouTube = new RadioButton("external", "YouTube");
	private final TextBox textBoxExternalUrl = new TextBox();
	private final HorizontalPanel panelExternalUrl = new HorizontalPanel();
	private final TextBox textBoxCreator = new TextBox();
	private final TextArea textAreaNote = new TextArea();
	private final CreationUi creationUi;
	private final Label labelAnswerCounter = new Label("0/0");
	private final Button buttonResetAnswerCount = new Button("リセット", this);
	@VisibleForTesting
	final HTML htmlPlusOne = new HTML();
	private final Label labelGood = new Label("0");
	private final Button buttonResetVote = new Button("リセット");
	private final CheckBox checkBoxImageChoice = new CheckBox("画像選択肢");
	private final CheckBox checkBoxImageAnswer = new CheckBox("画像回答");
	private final Button buttonRemovePlayerAnswers = new Button("誤回答を削除する", this);
	private int problemId = -1;
	private final List<Button> buttonPolygonCreation = new ArrayList<Button>();
	private final VerticalPanel panelProblemFeedback = new VerticalPanel();
	private final Button buttonClearProblemFeedback = new Button("クリア", this);
	private Date indication;
	private Date indicationResolved;
	private final Button buttonIndicate = new Button("問題の不備を指摘する");
	@VisibleForTesting
	final CheckBox checkBoxUnindicate = new CheckBox("指摘マークを消す (ページ下の掲示板の指摘内容を確認して下さい)");

	public WidgetProblemForm(CreationUi creationUi) {
		this.creationUi = creationUi;

		// 問題番号
		add(labelProblemNumber);

		Grid grid = new Grid(14, 2);
		grid.addStyleName("gridFrame");
		grid.addStyleName("gridFontNormal");

		int row = 0;

		// ジャンル
		for (ProblemGenre genre : ProblemGenre.values()) {
			String name = genre == ProblemGenre.Random ? "ジャンルを選んでください" : genre.toString();
			listBoxGenre.addItem(name, Integer.toString(genre.getIndex()));
		}
		listBoxGenre.setWidth("200px");
		grid.setText(row, 0, "ジャンル");
		grid.setWidget(row++, 1, listBoxGenre);

		// 出題形式
		for (ProblemType type : ProblemType.values()) {
			if (ProblemType.Random1.compareTo(type) <= 0) {
				break;
			}
			String item = type == ProblemType.Random ? "出題形式を選んでください" : type.toString();
			listBoxType.addItem(item, type.name());
		}
		listBoxType.setWidth("200px");
		listBoxType.addChangeHandler(this);
		grid.setText(row, 0, "出題形式");
		grid.setWidget(row++, 1, listBoxType);

		// ランダムフラグ
		for (int i = 1; i <= 5; ++i) {
			String s = Integer.toString(i);
			listBoxRandomFlag.addItem(s, RandomFlag.values()[i].name());
		}
		listBoxRandomFlag.setSelectedIndex(4);
		listBoxRandomFlag.setWidth("100px");
		grid.setText(row, 0, "ランダムフラグ");
		grid.setWidget(row++, 1, listBoxRandomFlag);

		// 問題文
		textAreaSentence.setCharacterWidth(60);
		textAreaSentence.setVisibleLines(5);
		grid.setText(row, 0, "問題文");
		grid.setWidget(row++, 1, textAreaSentence);

		// 選択肢
		VerticalPanel choicePanel = new VerticalPanel();
		choicePanel.setStyleName("gridNoFrame");
		choicePanel.addStyleName("gridFontNormal");
		for (int i = 0; i < MAX_NUMBER_OF_CHOICES; ++i) {
			textBoxChoice[i] = new TextBox();
			textBoxChoice[i].setWidth("400px");
			choicePanel.add(textBoxChoice[i]);
		}
		choicePanel.add(checkBoxImageChoice);
		grid.setText(row, 0, "選択肢");
		grid.setWidget(row++, 1, choicePanel);

		// 解答
		VerticalPanel answerPanel = new VerticalPanel();
		answerPanel.setStyleName("gridNoFrame");
		answerPanel.addStyleName("gridFontNormal");
		for (int i = 0; i < MAX_NUMBER_OF_ANSWERS; ++i) {
			HorizontalPanel panel = new HorizontalPanel();
			panel.setVerticalAlignment(ALIGN_MIDDLE);
			answerPanel.add(panel);

			textBoxAnswer[i] = new TextBox();
			textBoxAnswer[i].setWidth("400px");
			panel.add(textBoxAnswer[i]);

			Button button = new Button("領域作成", this);
			panel.add(button);
			buttonPolygonCreation.add(button);
		}
		answerPanel.add(checkBoxImageAnswer);
		grid.setText(row, 0, "解答");
		grid.setWidget(row++, 1, answerPanel);

		// 外部コンテンツ
		grid.setText(row, 0, "外部コンテンツ");
		{
			VerticalPanel panel = new VerticalPanel();
			panel.setStyleName("gridNoFrame");
			panel.addStyleName("gridFontNormal");
			{
				radioButtonNone.setValue(true);
				radioButtonNone.addClickHandler(this);
				radioButtonImage.addClickHandler(this);
				radioButtonYouTube.addClickHandler(this);

				HorizontalPanel panel2 = new HorizontalPanel();
				panel2.add(radioButtonNone);
				panel2.add(radioButtonImage);
				panel2.add(radioButtonYouTube);
				panel.add(panel2);
			}
			{
				textBoxExternalUrl.setWidth("400px");

				panelExternalUrl.add(new Label("URL"));
				panelExternalUrl.add(textBoxExternalUrl);
				panel.add(panelExternalUrl);
			}
			grid.setWidget(row++, 1, panel);
		}

		// 作成者
		textBoxCreator.setWidth("200px");
		textBoxCreator.setMaxLength(6);
		textBoxCreator.setText(UserData.get().getPlayerName());
		grid.setText(row, 0, "問題作成者");
		grid.setWidget(row++, 1, textBoxCreator);

		// 回答数
		grid.setText(row, 0, "回答数");
		HorizontalPanel panelAnswerCount = new HorizontalPanel();
		panelAnswerCount.add(labelAnswerCounter);
		panelAnswerCount.add(buttonResetAnswerCount);
		panelAnswerCount.add(buttonRemovePlayerAnswers);
		buttonRemovePlayerAnswers.setVisible(false);
		grid.setWidget(row++, 1, panelAnswerCount);

		// 回答数
		grid.setText(row, 0, "+1");
		HorizontalPanel panelVoteCount = new HorizontalPanel();
		panelVoteCount.add(htmlPlusOne);
		grid.setWidget(row++, 1, panelVoteCount);

		// 評価
		grid.setText(row, 0, "良問");
		HorizontalPanel panelGood = new HorizontalPanel();
		panelGood.add(labelGood);
		panelGood.add(buttonResetVote);
		buttonResetVote.addClickHandler(this);
		grid.setWidget(row++, 1, panelGood);

		// 指摘
		grid.setText(row, 0, "指摘");
		HorizontalPanel panelIndicate = new HorizontalPanel();
		buttonIndicate.setVisible(false);
		checkBoxUnindicate.setVisible(false);
		panelIndicate.add(buttonIndicate);
		panelIndicate.add(checkBoxUnindicate);
		grid.setWidget(row++, 1, panelIndicate);
		buttonIndicate.addClickHandler(this);

		// 問題ノート
		grid.setText(row, 0, "問題ノート");
		textAreaNote.setCharacterWidth(60);
		textAreaNote.setVisibleLines(5);
		grid.setWidget(row++, 1, textAreaNote);

		// 問題評価
		grid.setText(row, 0, "問題評価");
		grid.setWidget(row++, 1, panelProblemFeedback);

		add(grid);

		updateForm();
	}

	/**
	 * 問題を設定する
	 * 
	 * @param problem
	 *            問題
	 * @param copy
	 *            問題をコピーして新しい問題を作成する場合は{@code true}、既存の問題を修正する場合は{@code false}
	 */
	public void setProblem(PacketProblem problem, boolean copy) {
		// TODO 範囲外の値をセットしようとしたときにJavaとブラウザで挙動が違うのを報告する
		listBoxGenre.setSelectedIndex(problem.genre.getIndex());
		listBoxType.setSelectedIndex(problem.type.getIndex());
		listBoxRandomFlag.setSelectedIndex(problem.randomFlag.getIndex() - 1);
		textAreaSentence.setText(problem.getProblemCreationSentence());

		if (problem.answers != null) {
			for (int i = 0; i < problem.answers.length; ++i) {
				String answer = nullToEmpty(problem.answers[i]);
				textBoxAnswer[i].setText(answer);
			}
		}

		if (problem.choices != null) {
			for (int i = 0; i < problem.choices.length; ++i) {
				String choice = nullToEmpty(problem.choices[i]);
				textBoxChoice[i].setText(choice);
			}
		}

		if (problem.creator != null) {
			textBoxCreator.setText(problem.creator);
		}

		labelAnswerCounter.setText(problem.good + "/" + problem.bad);

		if (copy) {
			htmlPlusOne.setHTML("");
		} else {
			htmlPlusOne.setHTML(PlusOne.getButton(problem.id, true));
			PlusOne.render();
		}

		textAreaNote.setText(problem.note.trim());
		checkBoxImageAnswer.setValue(problem.imageAnswer);
		checkBoxImageChoice.setValue(problem.imageChoice);

		problemId = -1;
		if (!copy) {
			problemId = problem.id;
		}

		if (problemId == -1) {
			labelProblemNumber.setText("新規問題入力中");
			buttonRemovePlayerAnswers.setVisible(false);
		} else {
			labelProblemNumber.setText("問題番号" + problemId + "の問題を修正中");
			buttonRemovePlayerAnswers.setVisible(true);
		}

		if (problem.imageUrl != null) {
			textBoxExternalUrl.setText(problem.imageUrl);
			radioButtonImage.setValue(true);

		} else if (problem.movieUrl != null) {
			textBoxExternalUrl.setText(problem.movieUrl);
			radioButtonYouTube.setValue(true);

		} else {
			radioButtonNone.setValue(true);
		}

		// 良問
		labelGood.setText(String.valueOf(problem.voteGood));

		// 指摘
		if (problem.indication == null) {
			indication = null;
			indicationResolved = problem.indicationResolved;
			buttonIndicate.setVisible(true);
			checkBoxUnindicate.setVisible(false);
		} else {
			indication = problem.indication;
			indicationResolved = null;
			buttonIndicate.setVisible(false);
			checkBoxUnindicate.setVisible(true);
		}
		checkBoxImageAnswer.setValue(false);

		// 問題評価文
		panelProblemFeedback.clear();
		if (problemId != -1) {
			Service.Util.getInstance().getProblemFeedback(problemId, callbackGetProblemFeedback);
		}

		updateForm();
	}

	private final AsyncCallback<List<String>> callbackGetProblemFeedback = new AsyncCallback<List<String>>() {
		public void onSuccess(List<String> result) {
			panelProblemFeedback.add(new HTML(new SafeHtmlBuilder().appendEscapedLines(
					Joiner.on('\n').join(result)).toSafeHtml()));

			if (!result.isEmpty()) {
				panelProblemFeedback.add(buttonClearProblemFeedback);
			}
		}

		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "問題フィードバッグの取得に失敗しました", caught);
		}
	};

	private void updateForm() {
		ProblemType type = getSelectedType();

		creationUi.setTypeDescription(type.getDescription());

		for (TextBox textBox : textBoxChoice) {
			textBox.setEnabled(false);
			textBox.setVisible(false);
		}
		for (TextBox textBox : textBoxAnswer) {
			textBox.setEnabled(false);
			textBox.setVisible(false);
		}

		for (int i = 0; i < type.getNumberOfAnswers(); ++i) {
			textBoxAnswer[i].setEnabled(true);
			textBoxAnswer[i].setVisible(true);
		}
		for (int i = type.getNumberOfAnswers(); i < MAX_NUMBER_OF_ANSWERS; ++i) {
			textBoxAnswer[i].setText("");
		}

		for (int i = 0; i < type.getNumberOfChoices(); ++i) {
			textBoxChoice[i].setEnabled(true);
			textBoxChoice[i].setVisible(true);
		}
		for (int i = type.getNumberOfChoices(); i < MAX_NUMBER_OF_CHOICES; ++i) {
			textBoxChoice[i].setText("");
		}

		checkBoxImageAnswer.setVisible(type.isImageAnswer());
		checkBoxImageChoice.setVisible(type.isImageChoice());

		for (Button button : buttonPolygonCreation) {
			button.setVisible(type.isPolygonCreation());
		}

		if (radioButtonNone.getValue()) {
			textBoxExternalUrl.setText("");
			panelExternalUrl.setVisible(false);
		} else {
			panelExternalUrl.setVisible(true);
		}
	}

	public void setEnable(boolean enabled) {
		List<FocusWidget> focusWidgets = Lists.newArrayList(listBoxGenre, listBoxType,
				listBoxRandomFlag, textAreaSentence, textBoxAnswer[0], textBoxAnswer[1],
				textBoxAnswer[2], textBoxAnswer[3], textBoxChoice[0], textBoxChoice[1],
				textBoxChoice[2], textBoxChoice[3], radioButtonNone, radioButtonImage,
				radioButtonYouTube, textBoxExternalUrl, textBoxCreator, textAreaNote,
				buttonResetAnswerCount, checkBoxImageChoice, checkBoxImageAnswer,
				buttonRemovePlayerAnswers, buttonClearProblemFeedback, buttonResetVote);
		focusWidgets.addAll(buttonPolygonCreation);

		for (FocusWidget focusWidget : focusWidgets) {
			focusWidget.setEnabled(enabled);
		}
	}

	/**
	 * 問題を取得する
	 * 
	 * @return 問題
	 */
	public PacketProblem getProblem() {
		PacketProblem problem = new PacketProblem();

		// 問題番号
		problem.id = problemId;

		// ジャンル・出題形式
		problem.genre = ProblemGenre.values()[Integer.parseInt(listBoxGenre.getValue(listBoxGenre
				.getSelectedIndex()))];
		problem.type = getSelectedType();
		problem.randomFlag = RandomFlag.values()[listBoxRandomFlag.getSelectedIndex() + 1];

		// 問題文
		problem.setSentence(textAreaSentence.getText());

		// 答え
		problem.answers = new String[MAX_NUMBER_OF_ANSWERS];
		for (int i = 0; i < MAX_NUMBER_OF_ANSWERS; ++i) {
			problem.answers[i] = emptyToNull(textBoxAnswer[i].getText().replaceAll("&", "＆"));

			if (problem.type == ProblemType.Typing || problem.type == ProblemType.Effect
					|| problem.type == ProblemType.Flash) {
				problem.answers[i] = StringUtils.toFullWidth(problem.answers[i]);
			}
		}

		// 選択肢
		problem.choices = new String[MAX_NUMBER_OF_CHOICES];
		for (int i = 0; i < MAX_NUMBER_OF_CHOICES; ++i) {
			problem.choices[i] = emptyToNull(textBoxChoice[i].getText().replaceAll("&", "＆"));
		}

		if (problem.type == ProblemType.YonTaku || problem.type == ProblemType.Rensou) {
			problem.answers = new String[MAX_NUMBER_OF_ANSWERS];
			problem.answers[0] = problem.choices[0];
		}

		if (radioButtonImage.getValue()) {
			problem.imageUrl = textBoxExternalUrl.getText();
		} else if (radioButtonYouTube.getValue()) {
			problem.movieUrl = textBoxExternalUrl.getText();
		}

		// 作成者
		problem.creator = textBoxCreator.getText();

		// 回答数
		String answerCount[] = labelAnswerCounter.getText().split("/");
		problem.good = Integer.parseInt(answerCount[0]);
		problem.bad = Integer.parseInt(answerCount[1]);

		// 投票数
		problem.voteGood = Integer.valueOf(labelGood.getText());

		// 指摘
		if (indication != null) {
			if (!checkBoxUnindicate.getValue()) {
				problem.indication = indication;
				problem.indicationResolved = null;
			} else {
				// 指摘を解除した場合
				problem.indication = null;
				problem.indicationResolved = new Date();
			}
		} else {
			problem.indication = null;
			problem.indicationResolved = indicationResolved;
		}

		// 問題ノート
		problem.note = textAreaNote.getText().trim();
		if (problem.note.length() > MAX_PROBLEM_NOTE_LENGTH) {
			problem.note = problem.note.substring(0, MAX_PROBLEM_NOTE_LENGTH);
		}

		problem.imageAnswer = isImageAnswer();
		problem.imageChoice = isImageChoice();

		return problem;
	}

	private ProblemType getSelectedType() {
		int selectedIndex = listBoxType.getSelectedIndex();
		String value = listBoxType.getValue(selectedIndex);
		return ProblemType.valueOf(value);
	}

	private void resetAnswerCount() {
		labelAnswerCounter.setText("0/0");
	}

	private void removePlayerAnswer() {
		Service.Util.getInstance().removePlayerAnswers(problemId, callbackRemovePlayerAnswers);
	}

	private final AsyncCallback<Void> callbackRemovePlayerAnswers = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "誤解答の削除に失敗しました", caught);
		}
	};

	private void clearProblemFeedback() {
		if (problemId == -1) {
			return;
		}

		panelProblemFeedback.clear();
		Service.Util.getInstance().clearProblemFeedback(problemId, callbackClearProblemFeedback);
	}

	private final AsyncCallback<Void> callbackClearProblemFeedback = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "問題フィードバックの削除に失敗しました", caught);
		}
	};

	private boolean isImageAnswer() {
		ProblemType type = getSelectedType();
		return type.isImageAnswer() && checkBoxImageAnswer.getValue();
	}

	private boolean isImageChoice() {
		ProblemType type = getSelectedType();
		return type.isImageChoice() && checkBoxImageChoice.getValue();
	}

	@Override
	public void onClick(ClickEvent event) {
		Widget sender = (Widget) event.getSource();
		if (sender == buttonResetAnswerCount) {
			resetAnswerCount();
		} else if (sender == buttonRemovePlayerAnswers) {
			removePlayerAnswer();
		} else if (buttonPolygonCreation.contains(sender)) {
			final int answerIndex = buttonPolygonCreation.indexOf(sender);
			final DialogBoxPolygonCreation polygonCreation = new DialogBoxPolygonCreation(
					textBoxChoice[0].getText(), textBoxAnswer[answerIndex].getText());
			polygonCreation.setPopupPosition(Window.getScrollLeft() + 10,
					Window.getScrollTop() + 10);
			polygonCreation
					.addDialogBoxPolygonCreationListener(new DialogBoxPolygonCreationListener() {
						public void onOk() {
							textBoxAnswer[answerIndex].setText(polygonCreation
									.getPolygonDescription());
						}

						public void onCancel() {
						}
					});
			polygonCreation.show();
		} else if (sender == buttonClearProblemFeedback) {
			clearProblemFeedback();
		} else if (sender == radioButtonNone) {
			updateForm();
		} else if (sender == radioButtonImage) {
			updateForm();
		} else if (sender == radioButtonYouTube) {
			updateForm();
		} else if (sender == buttonIndicate) {
			onButtonIndicate();
		} else if (sender == buttonResetVote) {
			resetVote();
		}
	}

	public void onButtonIndicate() {
		final String message = Window.prompt("指摘内容をお書きください", "(指摘内容をお書きください)");
		if (message == null) {
			return;
		}

		if (indicationResolved != null
				&& System.currentTimeMillis() < indicationResolved.getTime() + 7L * 24 * 60 * 60
						* 1000) {
			// 指摘解除されてから7日以内の場合
			AsyncCallback<List<PacketBbsThread>> callbackGetBbsThreads = new AsyncCallback<List<PacketBbsThread>>() {
				@Override
				public void onSuccess(List<PacketBbsThread> result) {
					if (result == null || result.isEmpty()) {
						logger.log(Level.WARNING, "スレッドの取得に失敗しました");
						return;
					}

					PacketBbsThread thread = result.get(0);
					PacketBbsResponse response = new PacketBbsResponse();
					response.threadId = thread.id;
					response.name = UserData.get().getPlayerName();
					response.userCode = UserData.get().getUserCode();
					response.dispInfo = 2;
					response.postTime = System.currentTimeMillis();
					response.body = message;

					Service.Util.getInstance().writeToBbs(response, true, callbackBuildBbsThread);
				}

				@Override
				public void onFailure(Throwable caught) {
				}
			};
			Service.Util.getInstance().getBbsThreads(problemId, 0, 1, callbackGetBbsThreads);
		} else {
			// 初めて指摘する場合、または指摘解除されてから7日以上経過した場合
			PacketBbsThread thread = new PacketBbsThread();
			thread.title = "不具合が指摘されました (" + Utility.toDateFormat(new Date()) + ")";
			PacketBbsResponse response = new PacketBbsResponse();
			response.body = message;
			response.dispInfo = 2; // 全て表示
			response.name = UserData.get().getPlayerName();
			response.userCode = UserData.get().getUserCode();

			Service.Util.getInstance().buildBbsThread(problemId, thread, response,
					callbackBuildBbsThread);
		}
	}

	private final AsyncCallback<Void> callbackBuildBbsThread = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
			creationUi.reloadBbs();
			indication = new Date();
			Service.Util.getInstance().indicateProblem(problemId, callbackPointOutInProblem);
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "BBSスレッド立てに失敗しました", caught);
		}
	};
	private final AsyncCallback<Void> callbackPointOutInProblem = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
			indication = new Date();
			checkBoxUnindicate.setValue(false);
			setProblem(getProblem(), false);

			if (UserData.get().isRegisterIndicatedProblem()) {
				Service.Util.getInstance().addProblemIdsToReport(UserData.get().getUserCode(),
						ImmutableList.of(problemId), callbackAddProblemIdsToReport);
			}

			Window.alert("ご指摘ありがとうございました");
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "指摘フラグの更新に失敗しました", caught);
		}
	};
	private final AsyncCallback<Void> callbackAddProblemIdsToReport = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "正解率統計への問題追加に失敗しました", caught);
		}
	};

	@Override
	public void onChange(ChangeEvent event) {
		Widget sender = (Widget) event.getSource();
		if (sender == listBoxType) {
			updateForm();
		}
	}

	private final AsyncCallback<Void> callbackResetVote = new AsyncCallback<Void>() {
		@Override
		public void onSuccess(Void result) {
			labelGood.setText("0");
		}

		@Override
		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "投票のリセットに失敗しました", caught);
		}
	};

	private void resetVote() {
		Service.Util.getInstance().resetVote(problemId, callbackResetVote);
	}
}
